import type { ShapeType } from './interface';
import { DEFAULTLAYER } from './constant/index';
import { throttle } from './utils';
import Recorder from './core/recorder';
import BasicLine from './core/shapes/basicLine';
import StraightLine from './core/shapes/straightLine';
import Rectangle from './core/shapes/rectangle';
import Circle from './core/shapes/circle';
import Image from './core/shapes/image';
import CreateImage from './core/shapes/createShape/createImage';

export type DrawType = 'basicline' | 'straightline' | 'arrowline' | 'rectangle' | 'circle';

interface DrawingProps {
  el: HTMLElement;
  lineWidth?: number;
  lineColor?: string;
  drawType?: DrawType;
  backgroundColor?: string;
}

interface CreateImageType {
  src: string;
  width?: number;
  height?: number;
  left?: number;
  top?: number;
  layer?: number;
}

let animationId: number;

class Drawing {
  private container: HTMLDivElement;
  private lineColor: string;
  private canvas: HTMLCanvasElement | null;
  private ctx: CanvasRenderingContext2D | null;
  private drawType: DrawType;
  private lineWidth: number;
  private shapes: { [key: number]: ShapeType[] };
  private backgroundColor: string;
  private steps: number[];
  private recorder: Recorder | null;

  constructor(props: DrawingProps) {
    this.canvas = null;
    this.ctx = null;
    this.lineColor = props.lineColor || '#000';
    this.lineWidth = props.lineWidth || 1;
    this.drawType = props.drawType || 'basicline';
    this.backgroundColor = props.backgroundColor || '#eee';
    this.shapes = {};
    this.steps = []; // 撤销记录
    this.container = this.initContainer(props.el);
    this.recorder = null;
    this.initCanvas();
  }

  /**
   * 设置线条颜色
   * @param color rgb
   */
  setLineColor(color: string) {
    this.lineColor = color;
  }

  /**
   * 设置线条宽度
   * @param width
   */
  setLineWidth(width: number) {
    this.lineWidth = width;
  }

  /**
   * 设置绘制类型
   * @param drawType
   */
  setDrawType(drawType: DrawType) {
    this.drawType = drawType;
  }

  /**
   * 撤销
   */
  goback() {
    const lastStep = this.steps.pop();
    if (lastStep || lastStep === 0) {
      this.shapes[lastStep].pop();
      this.draw();
    }
  }

  /**
   * 创建图片
   * @param props
   */
  createImage({ src, width, height, left, top, layer }: CreateImageType) {
    let imageBox: CreateImage | null = new CreateImage({ container: this.container });
    imageBox.createImage({ src, width, height, left, top }, (imgInfo) => {
      const image = new Image({
        image: imgInfo.image,
        startX: imgInfo.left,
        startY: imgInfo.top,
        width: imgInfo.width,
        height: imgInfo.height,
      });
      image.setLayer(layer ? layer + DEFAULTLAYER : image.getLayer());
      this.shapes[image.getLayer()] = this.shapes[image.getLayer()] || [];
      this.shapes[image.getLayer()].push(image);
      this.steps.push(image.getLayer());

      this.draw();
      imageBox = null;
    });
  }

  /**
   * 开始录制
   */
  startRecorder() {
    this.recorder = new Recorder({
      canvas: this.canvas!,
      frameRate: 15,
    });
    this.recorder.start();
  }

  stopRecorder() {
    if (!this.recorder) {
      throw new Error('请先调用 startRecorder 方法');
    }
    return this.recorder.stop();
  }

  /**
   * 创建容器
   * @param dom
   * @returns HTMLDivElement
   */
  private initContainer(dom: HTMLElement) {
    const container = document.createElement('div');
    container.style.position = 'relative';
    container.style.width = '100%';
    container.style.height = '100%';
    dom.appendChild(container);
    return container;
  }

  /**
   * 初始化画布
   */
  private initCanvas() {
    const cWidth = this.container.clientWidth;
    const cHeight = this.container.clientHeight;
    const canvas = document.createElement('canvas');
    canvas.width = cWidth;
    canvas.height = cHeight;
    canvas.style.width = '100%';
    canvas.style.height = '100%';
    canvas.style.cursor = 'crosshair';
    canvas.style.background = this.backgroundColor;
    this.container.appendChild(canvas);

    this.canvas = canvas;
    this.ctx = canvas.getContext('2d');
    this.registerBaseShapeEvent();
  }

  /**
   * 注册事件
   */
  private registerBaseShapeEvent() {
    const rect = this.canvas!.getBoundingClientRect();
    let insideShape: ShapeType | null = null;

    const canvasMouseMove = (e: MouseEvent) => {
      if (Object.keys(this.shapes).length === 0) return;
      const moveX = e.clientX - rect.left;
      const moveY = e.clientY - rect.top;
      insideShape = this.getInsideShape(moveX, moveY, this.drawType);
      if (insideShape) {
        this.canvas!.style.cursor = 'pointer';
      } else {
        this.canvas!.style.cursor = 'crosshair';
      }
    };
    const throttledMouseMove = throttle(canvasMouseMove, 300);

    this.canvas!.onmousedown = (e) => {
      this.canvas!.onmousemove = null;
      this.drawFrame();
      let startX = e.clientX - rect.left;
      let startY = e.clientY - rect.top;
      let shape: ShapeType;
      if (!insideShape) {
        shape = this.getBaseShapeType(startX, startY);
        const layer = shape.getLayer();
        this.shapes[layer] = this.shapes[layer] || [];
        this.shapes[layer].push(shape);
        this.steps.push(layer);
      } else {
        shape = insideShape;
      }

      window.onmousemove = (e) => {
        let endX = e.clientX - rect.left;
        let endY = e.clientY - rect.top;
        if (endX <= 0) endX = 0 + this.lineWidth;
        if (endX >= rect.width) endX = rect.width - this.lineWidth;
        if (endY <= 0) endY = 0 + this.lineWidth;
        if (endY >= rect.height) endY = rect.height - this.lineWidth;
        if (insideShape) {
          let dx = endX - startX;
          let dy = endY - startY;
          if (shape.maxX && shape.maxX + dx >= rect.width) dx = 0;
          if (shape.minX && shape.minX + dx <= 0) dx = 0;
          if (shape.maxY && shape.maxY + dy >= rect.height) dy = 0;
          if (shape.minY && shape.minY + dy <= 0) dy = 0;
          shape?.changePosition?.(dx, dy);
          startX = endX;
          startY = endY;
        } else {
          shape?.setEnd?.(endX, endY);
        }
      };
      window.onmouseup = () => {
        cancelAnimationFrame(animationId);
        window.onmousemove = null;
        window.onmouseup = null;
        this.canvas!.onmousemove = throttledMouseMove;
      };
    };

    this.canvas!.ondblclick = (e) => {
      const startX = e.clientX - rect.left;
      const startY = e.clientY - rect.top;
      for (const key in this.shapes) {
        const shapes = this.shapes[key] || [];
        for (let index = shapes.length - 1; index >= 0; index--) {
          const shape = shapes[index];
          if (shape instanceof Image) {
            if (shape.inside(startX, startY)) {
              this.createImage({
                src: shape.src,
                width: shape.width,
                height: shape.height,
                left: shape.startX,
                top: shape.startY,
              });
              this.shapes[key].splice(index, 1);
              this.draw();
              break;
            }
          }
        }
      }
    };
  }

  private getInsideShape(x: number, y: number, type: string): ShapeType | null {
    for (const key in this.shapes) {
      const shapes = this.shapes[key] || [];
      for (let i = shapes.length - 1; i >= 0; i--) {
        const shape = shapes[i];
        if (shape.getType() === type && shape?.inside?.(x, y)) {
          return shape;
        }
      }
    }

    return null;
  }

  private getBaseShapeType(startX: number, startY: number): ShapeType {
    const props = { startX, startY };
    let shape = {} as ShapeType;
    switch (this.drawType) {
      case 'basicline':
        shape = new BasicLine(props);
        break;
      case 'straightline':
        shape = new StraightLine(props);
        break;
      case 'rectangle':
        shape = new Rectangle(props);
        break;
      case 'circle':
        shape = new Circle(props);
        break;
      default:
        shape = new BasicLine(props);
        break;
    }
    return shape;
  }

  private draw() {
    this.ctx!.fillStyle = this.backgroundColor;
    this.ctx!.fillRect(0, 0, this.canvas?.width || 0, this.canvas?.height || 0);

    this.ctx!.lineWidth = this.lineWidth;
    this.ctx!.strokeStyle = this.lineColor;
    for (const key in this.shapes) {
      const shapes = this.shapes[key];
      if (shapes && shapes.length) {
        for (const shape of shapes) {
          shape?.draw?.(this.ctx!);
        }
      }
    }
  }

  private drawFrame() {
    animationId = requestAnimationFrame(this.drawFrame.bind(this));
    this.draw();
  }
}

export default Drawing;
